package galaxy;


import galaxy.drawer.action.Action;
import galaxy.drawer.action.ActionStack;
import galaxy.drawer.action.BranchAction;
import galaxy.drawer.action.DrawAction;
import galaxy.drawer.action.ExplodeAction;
import galaxy.drawer.action.GoAction;
import galaxy.drawer.action.LeafAction;
import galaxy.drawer.action.StackAction;
import galaxy.generator.Grammar;
import galaxy.generator.Rule;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;

import processing.core.PVector;

import static java.lang.Math.*;

public abstract class AbstractModel {
	private Random random = new Random();
	private String start;
	private Grammar grammar = new Grammar();
	private Map<String, Action> actions = new LinkedHashMap<String, Action>();
	private int rounds = 1;
	private StackAction stack = new StackAction();
	private PVector drawStartPosition = new PVector((float) 0.5, (float) 0.5);
	private double startAngle;
	
	public AbstractModel()
	{
		init();
	}
	
	public abstract void init();
	
	public float getStartAngle()
	{
		return (float) startAngle;
	}
	
	public PVector getDrawStartPosition()
	{
		return drawStartPosition;
	}
	  
	public float random(double min, double max) {
		return random.nextFloat() * ((float) max - (float) min)
				+ (float) min;
	}

	public int random(int min, int max) {
		return random.nextInt(max - min) + min;
	}
	
	public AbstractModel atAngle(double angle)
	{
		this.startAngle = angle;
		return this;
	}
	
	public AbstractModel startDrawingFrom(double x, double y)
	{
		drawStartPosition.x = (float) x;
		drawStartPosition.y = (float) y;
		return this;
	}
	
	public double top()
	{
		return 0;
	}
	
	public double bottom()
	{
		return 1;
	}
	
	public double left()
	{
		return 0;
	}
	
	public double right()
	{
		return 1;
	}
	
	public double center()
	{
		return 0.5;
	}
	
	public double degrees(double amount)
	{
		return amount / 180 * PI;
	}
	
	public Interpreter on(String input)
	{
		return new Interpreter(input);
	}
	
	public AbstractModel startFrom(String start)
	{
		this.start = start;
		return this;
	}
	
	public AbstractModel rounds(int rounds)
	{
		this.rounds = rounds;
		return this;
	}
	
	public String getStart()
	{
		if (null == start) {
			throw new RuntimeException("Start needs to be defined!");
		}
		return start;
	}
	
	public Grammar getGrammar()
	{
		return grammar;
	}
	
	public Map<String, Action> getActions()
	{
		return actions;
	}
	
	public int getRounds()
	{
		return rounds;
	}
	
	public int hsb(int hue, int saturation, int brightness)
	{
		return (255 << 24) | (hue << 16) | (saturation << 8) | brightness;
	}
	
	public class Interpreter
	{
		private String input;
		private ActionStack actions = new ActionStack();
		private DrawAction draw;
		private GoAction go;
		private ExplodeAction explode;
		
		public Interpreter(String input)
		{
			this.input = input;
			AbstractModel.this.actions.put(input, actions);
		}
		
		public ExplodeAction getExplodeAction()
		{
			if (null == explode) {
				explode = new ExplodeAction();
				actions.addLast(explode);
			}
			return explode;
		}
		
		public DrawAction getDrawAction()
		{
			if (null == draw) {
				draw = new DrawAction();
				actions.addLast(draw);
			}
			return draw;
		}
		
		public GoAction getGoAction()
		{
			if (null == go) {
				go = new GoAction();
				actions.addLast(go);
			}
			return go;
		}
		
		public Interpreter output(String output)
		{
			grammar.add(new Rule(input, output));
			return this;
		}
		
		public Interpreter whenLeaf()
		{
			AbstractModel.this.actions.put(input, new LeafAction(actions));
			return this;
		}
		
		public Interpreter whenBranch()
		{
			AbstractModel.this.actions.put(input, new BranchAction(actions));
			return this;
		}
		
		public ExplodeAction explode()
		{
			return getExplodeAction();
		}
		
		public Interpreter go(double length)
		{
			getGoAction().setLength((float) length);
			return this;
		}
		
		public Interpreter draw()
		{
			return draw(1);
		}
		
		public Interpreter draw(double length)
		{
			getDrawAction().setLength((float) length);
			return this;
		}
		
		public Interpreter rotate(double angle)
		{
			getDrawAction().setAngle((float) angle);
			return this;
		}
		
		public Interpreter pushContext()
		{
			actions.addLast(stack.getPushAction());
			return this;
		}
		
		public Interpreter popContext()
		{
			actions.addLast(stack.getPopAction());
			return this;
		}
	}
}
